/**
 * 
 */
package game;

import java.awt.Color;
import java.awt.geom.*;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 * @author patrick
 * 
 */
public class GamePanel extends JPanel implements ActionListener {

	/**
	 * generated
	 */
	private static final long serialVersionUID = 2503306633661631281L;
	private int gravityCount; // counts the steps. for gravity
	private Timer timer;
	private int aggregateChanges;
	private IceCube cube;
	private String finishText;
	private boolean pause;
	private ArrayList<Wall> walls;
	private Level level;
	private double slideLeftValue;
	private double slideRightValue;
	private int startValue;
	private boolean prevCollision;
	private int maxAggregateChanges;
	private int levelnr;
	private int changeDisplayTime;
	private long prevEventWhen; // when the action performed method was called
	// previously
	private boolean won;
	private int redScreenTime;
	int gameDuration;
	private int width;
	private int height;
	private Game frame;
//	private boolean finish;
	
	/**
	 * create a new GamePanel
	 * 
	 * @param levelNr
	 *            nr of level to be loaded
	 * */
	public GamePanel(int levelNr, int width, int height, Game pFrame) {
		
		frame = pFrame;
		levelnr = levelNr;
		won = false;
		redScreenTime = 0;
		changeDisplayTime = 1000;
		prevEventWhen = 0;
		prevCollision = false;
		startValue = 3;
		gameDuration = 0;
		gravityCount = 0;
		addKeyListener(new TAdapter(this));
		setFocusable(true);
		setBackground(Color.white);
		setDoubleBuffered(true);
		slideLeftValue = -startValue;
		slideRightValue = startValue;
		aggregateChanges = 0;
		cube = new IceCube(this);
		pause = false;

		this.width = width;
		this.height = height;
		this.loadLevel(levelnr, width, height);

		// backgorund image
		this.setBorder(javax.swing.BorderFactory.createMatteBorder(0, 0, 800,
				600, new ImageIcon(getClass().getResource(
						"/images/white_background.png"))));

		maxAggregateChanges = level.getMaxAggretateChanges();

		// set the timer for redrawing
		timer = new Timer(5, this);
		timer.start();
	}

	/**
	 * initialize selected level
	 * 
	 * @param levelnr
	 *            number of level to be loaded
	 * @param width
	 *            should be the same as in every level
	 * @param height
	 *            should be the same as in every level
	 * */
	private void loadLevel(int levelnr, int width, int height) {
		this.levelnr = levelnr;
		changeDisplayTime = 1000;
		redScreenTime = 0;
		gameDuration = 0;
		this.level = new Level(levelnr, width, height);
		finishText = level.getFinishText();
		maxAggregateChanges = level.getMaxAggretateChanges();
		aggregateChanges = 0;

		this.gravityCount = 0;
		cube.setX(level.getPlayerStartX());
		cube.setY(level.getPlayerStartY());
		cube.makeHard(true);

		walls = level.getWalls();
		won = false;
		cube.makeHard(true);
		timer = new Timer(5, this);
		timer.start();

		if(levelnr > Level.totalNrOfLevels){
	        this.setVisible(false);
	        BackgroundPanel gameOver = new BackgroundPanel(frame);
	        
	        frame.add(gameOver);   
	        frame.remove(this);
	        frame.validate();
		}
		Game.lastLevel = levelnr;
	}

	public int getMaxAggregateChanges() {
		return maxAggregateChanges;
	}

	public void setMaxAggregateChanges(int maxAggregateChanges) {
		this.maxAggregateChanges = maxAggregateChanges;
	}

	public int getAggregateChanges() {
		return aggregateChanges;
	}

	public void setAggregateChanges(int aggregateChanges) {
		this.aggregateChanges = aggregateChanges;
	}

	private void displayRedScreen(Graphics g) {
		g.setColor(Color.red);
		g.fillRect(0, 0, width, height);
		g.setColor(Color.black);

	}

	private void displayLevelInfo(Graphics g) {
		Font font2 = new Font("Verdana", Font.BOLD, 30);
		FontMetrics metr2 = this.getFontMetrics(font2);
		g.setFont(font2);

		ImageIcon ii = new ImageIcon(this.getClass().getResource(
				"/images/Info.png"));
		int x = (this.width - ii.getIconWidth()) / 2;
		int y = (this.height - ii.getIconHeight()) / 2;
		g.drawImage(ii.getImage(), x, y, this);
		String text = "Level " + levelnr;
		g.drawString(text, x + ii.getIconWidth() / 2 - metr2.stringWidth(text)
				/ 2 + 10, y - metr2.getHeight() / 2 + 20 + ii.getIconHeight()
				/ 2);

	}
	
	private void displayPauseInfo() {
		Font font2 = new Font("Verdana", Font.BOLD, 30);
		FontMetrics metr2 = this.getFontMetrics(font2);
		getGraphics().setFont(font2);

		ImageIcon ii = new ImageIcon(this.getClass().getResource(
				"/images/Info.png"));
		int x = (this.width - ii.getIconWidth()) / 2;
		int y = (this.height - ii.getIconHeight()) / 2;
		getGraphics().drawImage(ii.getImage(), x, y , this);
		String text = "GAME PAUSED";
		getGraphics().drawString(text, x +ii.getIconWidth()/2-40 ,y + ii.getIconHeight()/2 - metr2.getHeight()/2+15);
	}

	/**
	 * In the paint() method, we draw the scene. We get the image and the
	 * coordinates from the sprite class and the level class
	 * */
	public void paint(Graphics g) {
		gameDuration += timer.getDelay();
		super.paint(g);
		
		// draw the player's sprite
		Graphics2D g2d = (Graphics2D) g;
		g2d.drawImage(cube.getImage(), (int) cube.getX(), (int) cube.getY(),
				this);

		// draw Finish
		g2d.drawImage(level.getFinish().getImage(), level.getFinish().getX(),
				level.getFinish().getY(), this);
		Font font = new Font("Helvetica", Font.PLAIN, 24);
		FontMetrics metr = this.getFontMetrics(font);

		// draw Tooltips (might be empty)
		if (level.getTooltips() != null) {
			g.setFont(font);
			g.drawString(level.getTooltips(), 200, 550);
		}

		// draw walls of level
		for (int i = 0; i < walls.size(); i++) {

			g2d.setColor(walls.get(i).getColor());
			AffineTransform at = AffineTransform.getRotateInstance(walls.get(i)
					.getAngle(), walls.get(i).getX() + walls.get(i).getWidth(),
					walls.get(i).getY());
			g2d.fill(at.createTransformedShape(walls.get(i)));
		}

		// draw switches
		for (Switch s : level.getSwitches()) {
			g2d.drawImage(s.getImage(), s.getX(), s.getY(), this);
		}

		g2d.setColor(Color.BLACK);
		font = new Font("Verdana", Font.BOLD, 12);
		metr = this.getFontMetrics(font);
		int changes = this.maxAggregateChanges - this.aggregateChanges;
		String message = "Levelnr.: " + this.levelnr + " Changes left: "
				+ changes;

		g.setFont(font);
		g.drawString(message,// this.getWidth() -
				// metr.stringWidth(message)) /
				// 2
				3, 3 + metr.getHeight() / 2);
		printChanges(g);

		if (changes == 0 && redScreenTime < 20) {
			this.displayRedScreen(g);
			redScreenTime += timer.getDelay();
		}
		g.setFont(font);
		if (cube.isAerial()) {

			int timeSec = (int) Math
					.ceil((double) cube.getTimetofly() / 100000);
			String msg = "Time to Fly: " + timeSec + " sec";
			g.drawString(msg, this.getWidth() - metr.stringWidth(msg) - 3,
					3 + metr.getHeight() / 2);
		}
		if (gameDuration < 1000) {
			this.displayLevelInfo(g2d);

		}

		// if the level is won
		if (won) {
			showWinnerCeremony(g);
		}

		ImageIcon ii = new ImageIcon(this.getClass().getResource(
				"/images/tastatur.png"));
		g.drawImage(ii.getImage(), 0, height - ii.getIconHeight() - 20, this);
		// no idea what this does...
		Toolkit.getDefaultToolkit().sync();

		// Ziel info
		this.displayInfoBox(g2d, level.getFinish().getX(), level.getFinish()
				.getY() - 60, finishText);
		if(pause)
			displayPauseInfo();

		if(!checkPlayerInPanel() && !won)
		{
		    this.showGameOver(g2d);
		}
		g.dispose();

	}

	private void displayInfoBox(Graphics g, int x, int y, String text) {
		Font font2 = new Font("Verdana", Font.BOLD, 14);
		FontMetrics metr2 = this.getFontMetrics(font2);
		g.setFont(font2);

		ImageIcon ii = new ImageIcon(this.getClass().getResource(
				"/images/sprechblaseSmall.png"));
		g.drawImage(ii.getImage(), x, y, this);
		int yOffset = 15;// wegen sprechblsae
		int xOffset = -3;
		g.drawString(text, x + ii.getIconWidth() / 2 - metr2.stringWidth(text)
				/ 2 + xOffset, y - metr2.getHeight() + ii.getIconHeight() / 2
				+ yOffset);

	}

	public void printChanges(Graphics g) {

		int changes = maxAggregateChanges - aggregateChanges;

		String changesMsg;
		if (changes != 1)
			changesMsg = changes + " Changes";
		else
			changesMsg = changes + " Change";
		if (this.changeDisplayTime > 0) {

			int xInfo = (int) cube.getX() + 10;
			int yInfo = (int) cube.getY() - cube.getHeight() / 2 - 60;
			this.displayInfoBox(g, xInfo, yInfo, changesMsg);

			changeDisplayTime -= 5;
		}
	}

	public void updateText() {

	}

	public void gameOver(Graphics g) {
		// TODO Auto-generated method stub

	}

	private void showGameOver(Graphics g) {
		Font font = new Font("Verdana", Font.BOLD, 12);
		FontMetrics metr = this.getFontMetrics(font);

		g.setColor(Color.red);
		String msg = "Game Over! Press \"R\" key to restart...";
		g.drawString(msg, this.getWidth() / 2 - metr.stringWidth(msg) / 2, this
				.getHeight()
				/ 2 - metr.getHeight());
		g.setColor(Color.black);
	}
	private void showWinnerCeremony(Graphics g) {
		Font font = new Font("Verdana", Font.BOLD, 12);
		FontMetrics metr = this.getFontMetrics(font);

		g.setColor(Color.red);
		String msg = "Level Complete! Press \"A\" key to continue...";
		g.drawString(msg, this.getWidth() / 2 - metr.stringWidth(msg) / 2, this
				.getHeight()
				/ 2 - metr.getHeight());
		g.setColor(Color.black);
	}

	/**
	 * The actionPerformed() method is called every 5ms (depending on timer
	 * setting) We move the sprite (=player) and repaint the board.
	 * */
	public void actionPerformed(ActionEvent e) {

		if (pause)
			return;

		if (checkFinishCollision())
			won = true;

		// window size is hard coded...cast should work
		cube.setPanelHeight((int) this.getSize().getHeight());
		cube.setPanelWidth((int) this.getSize().getWidth());

		// System.out.println(e.getWhen() + " " + prevEventWhen);
		// change every 100ms
		if (e.getWhen() - prevEventWhen > 200) {
			// System.out.println(e.getWhen() + " " + prevEventWhen);
			cube.updateImage();
			level.update();
			prevEventWhen = e.getWhen();

		}

		// System.out.println("is hard" + cube.isHard());
		if (cube.isHard() && cube.isMoving() == false) {

			if (cube.isSlideLeft() && slideLeftValue != 0) {

				slideLeftValue *= 0.95;

				cube.slideLeft((int) slideLeftValue);
				if (Math.abs(slideLeftValue) < 0.1) {
					slideLeftValue = -startValue;
					cube.setSlideLeft(false);
				}
			}

			if (cube.isSlideRight() && slideLeftValue != 0) {

				slideRightValue *= 0.95;
				cube.slideLeft((int) slideRightValue);
				if (Math.abs(slideRightValue) < 0.1) {
					slideRightValue = startValue;
					cube.setSlideRight(false);
				}
			}
		}

		cube.move();

		double temp = cube.getY();
		// gravity
		if (!cube.isAerial()) {
			cube.setY((float) (cube.getY() + 0.005 * gravityCount));
			gravityCount++;
		}

		if (cube.isFluid() && (checkCollisions() != prevCollision)) {
			cube.setFluidImage();
			// System.out.println("change State to fluid");
		} else {
			if (cube.isFluid())
				cube.setDropImage();
			// System.out.println("change State to drop");
		}

		if (checkCollisions()) {
			gravityCount = 0;
			cube.setY((float) temp);
			cube.setGroundContact(true);

		} else {
			cube.setGroundContact(false);
		}

		// check for collision with swiches
		if (cube.isHard())
			checkSwitchCollison();

		repaint();
		this.prevCollision = cube.isGroundContact();
		

	}

	/**
	 * check for collisions (player with level-walls) <code>true</code> if cube
	 * collides
	 * */
	public boolean checkCollisions() {

		Rectangle rectCube = cube.getBounds();
		Rectangle2D r;

		for (int i = 0; i < walls.size(); i++) {

			r = (Rectangle2D) walls.get(i);
			if (rectCube.intersects(r) && walls.get(i).color == Color.cyan
					&& cube.isFluid())
				return false;

			if (rectCube.intersects(r))
				return true;

		}

		return false;

	}

	/**
	 * tests collision of a shape with any wall in current level
	 * **/
	public boolean checkCollisions(Rectangle2D testShape) {
		Rectangle2D r;
		boolean collides = false;
		for (int i = 0; i < walls.size(); i++) {
			r = walls.get(i).getBounds2D();
			if (testShape.intersects(r))
				collides = true;
		}
		return collides;
	}
	
	public boolean checkPlayerInPanel(){
	    if(cube.getY()> this.height)
		return false;
	    return true;
	}

	/**
	 * check for collions with switches <code>true</code> if cube collides
	 * */
	public boolean checkSwitchCollison() {
		Rectangle rectCube = cube.getBounds();
		Rectangle2D r;
		boolean collision = false;

		for (int i = 0; i < level.getSwitches().size(); i++) {
			r = (Rectangle2D) level.getSwitches().get(i).getBounds();
			if (rectCube.intersects(r)) {

				// change the switche's state
				// if(level.getSwitches().get(i).isOn())
				// level.getSwitches().get(i).switchOFF();
				// else
				level.getSwitches().get(i).switchON();

				// call the action method for colliding switch
				level.switchAction(levelnr, i);

				collision = true;
			}
		}
		return collision;
	}

	/**
	 * check for collions with switches <code>true</code> if cube collides
	 * */
	public boolean checkFinishCollision() {
		Rectangle rectCube = cube.getBounds();
		Rectangle2D r;
		boolean collision = false;

		r = (Rectangle2D) level.getFinish().getBounds();
		if (rectCube.intersects(r))
			collision = true;

		return collision;
	}

	/**
	 * customized Keylistener
	 * */
	private class TAdapter extends KeyAdapter {

		private GamePanel panel;
		
		public TAdapter(GamePanel gamePanel) {
			this.panel = gamePanel;
		}

		public void keyReleased(KeyEvent e) {
			cube.keyReleased(e);
		}

		public void keyPressed(KeyEvent e) {
			
			if(pause && e.getKeyCode() == KeyEvent.VK_ENTER){
		        setVisible(false);
		        MenuPanel menu = new MenuPanel(frame);
		        frame.add(menu);   
		        frame.remove(panel);
		        frame.validate();
			}

			if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
				displayPauseInfo();

				if (pause)
					pause = false;
				else
					pause = true;
			}

			if ((e.getKeyChar() == 'a' || e.getKeyChar() == 'A') && won) {
				levelnr++;
				loadLevel(levelnr, width, height);
				System.out.println("Load level nr:" + levelnr);
			} else if (e.getKeyChar() == 'r' || e.getKeyChar() == 'R') {
				System.out.println("levelnr: = " + levelnr);
				loadLevel(levelnr, width, height);
			} else {
				if ((e.getKeyChar() == 'a' || e.getKeyChar() == 'A')
						|| (e.getKeyChar() == 's' || e.getKeyChar() == 'S')
						|| (e.getKeyChar() == 'd' || e.getKeyChar() == 'D')) {
					changeDisplayTime = 1000;
					redScreenTime = 0;
				}
				cube.keyPressed(e);
			}

		}
	}

	/**
	 * @return the inGame
	 */
	public boolean isPause() {
		return pause;
	}

	/**
	 * @param inGame
	 *            the inGame to set
	 */
	public void setPause(boolean inGame) {
		this.pause = inGame;
	}

	/**
	 * @return the level
	 */
	public Level getLevel() {
		return level;
	}

	/**
	 * @return the cube
	 */
	public IceCube getCube() {
		return cube;
	}

}
